package com.shinbada.common.tree;

import com.google.common.collect.Lists;
import com.shinbada.common.utils.IdGen;
import com.shinbada.core.persistence.TreeEntity;
import com.shinbada.excel.util.Const;
import com.shinbada.exception.ServiceException;
import lombok.SneakyThrows;
import org.apache.shiro.util.CollectionUtils;
import org.springframework.beans.BeanUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * 针对 树结构数据 转化
 *
 * @author initcap
 * @date Created in 2021/1/31 17:11.
 */
public class ExcelTreeUtil {

    private static int maxWith = 0;

    private ExcelTreeUtil() {
        throw new IllegalArgumentException("util class can not instance!");
    }

    /**
     * 通过Excel读取到的数据转化成实体集合,
     *
     * @param data 读取数据
     * @param <T>  树泛型
     * @return 集合
     */
    public static <T extends TreeEntity> List<T> toEntity(TreeModle treeModel, List<List<String>> data, List<T> old) {
        if (CollectionUtils.isEmpty(data)) {
            return Lists.newArrayList();
        }
        List<T> readList;
        switch (treeModel) {
            case level:
                readList = toLevelEntity(data, old);
                break;
            case line:
                readList = toLineEntity(data, old);
                break;
            default:
                return Lists.newArrayList();
        }
        return readList;
    }

    /**
     * 将数据库中存储的树结构数据转化成Excel并导出
     *
     * @param treeModel 转化模式 {@link TreeModle}
     * @param data      继承了 {@link TreeEntity} 的实体数据
     * @param <T>       树泛型
     * @return 结果
     */
    public static <T extends TreeEntity> ExcelData toExcel(TreeModle treeModel, List<T> data) {
        switch (treeModel) {
            case level:
                return toLevelExcel(data);
            case line:
                return toLineExcel(data);
            default:
                return ExcelData.build();
        }
    }

    /**
     * 将数据转化给Excel导出
     * 这里的数据一定要经过排序
     *
     * @param entityList 实体集合
     * @param <T>
     * @return
     */
    private static <T extends TreeEntity> ExcelData toLineExcel(List<T> entityList) {
        // 整理出按照顺序的集合
        List<T> sortList = TreeUtil.treeToList(TreeUtil.buildLevelTreeSort(entityList));
        ExcelData excelData = ExcelData.build();
        List<List<String>> resultList = new ArrayList<>();
        int maxWith = 0;
        for (T entity : sortList) {
            int level = entity.getParentIds().split(TreeLevelUtil.SEPARATOR).length - 1;
            maxWith = Math.max(maxWith, level);
            List<String> data = Lists.newArrayListWithCapacity(level);
            for (int j = 0; j < level - 1; j++) {
                data.add("");
            }
            data.add(entity.getName());
            resultList.add(data);
        }
        excelData.with = maxWith;
        excelData.data = resultList;
        return excelData;
    }

    private static <T extends TreeEntity> ExcelData toLevelExcel(List<T> entityList) {
        maxWith = 0;
        // 整理成树结果数据
        List<T> treeList = TreeUtil.buildLevelTreeSort(entityList);
        ExcelData excelData = ExcelData.build();
        List<List<String>> resultList = new ArrayList<>();
        for (T entity : treeList) {
            List<String> data = Lists.newArrayList();
            levelOrderTree(entity, data, resultList);
        }
        excelData.with = maxWith;
        excelData.data = resultList;
        return excelData;
    }

    private static <T extends TreeEntity> void levelOrderTree(T entity, List<String> data, List<List<String>> resultList) {
        data.add(entity.getName());
        List<T> childrens = entity.getChildren();
        if (CollectionUtils.isEmpty(childrens)) {
            resultList.add(new ArrayList<>(data));
            maxWith = Math.max(maxWith, data.size());
            return;
        }
        for (int i = 0; i < childrens.size(); i++) {
            levelOrderTree(childrens.get(i), new ArrayList<>(data), resultList);
        }
    }

    /**
     * 这里其实就是用双指针的方式，进行深度优先前序遍历构建树
     *
     * @param data 数据源
     * @param old  数据库中的树
     * @param <T>  树泛型
     * @return 需要新增的树集合
     */
    private static <T extends TreeEntity> List<T> toLevelEntity(List<List<String>> data, List<T> old) {
        // 将要导入的目标数据进行树结构组装
        List<T> oldTree = TreeUtil.buildLevelTreeSort(old);
        if (oldTree.size() != 1) {
            throw new ServiceException("导入必须仅指定一个根节点导入!");
        }
        T oldEntity = oldTree.get(0);
        maxWith = data.get(0).size();
        List<T> result = Lists.newArrayList();
        for (int i = 0; i < data.size(); i++) {
            // 采用双指针形式构建,记录父指针和移动指针
            T pre = null, move = oldEntity;
            for (int j = 0; j < maxWith && !Const.XLSX_DEFAULT_EMPTY_CELL_VALUE.equals(data.get(i).get(j)); j++) {
                // 那到当前Excel存储的值
                String value = data.get(i).get(j);
                // 只有根节点，与导入的名字不一样的时候我们将根节点的名字进行替换
                if (i == 0 && j == 0 && !move.getName().equals(value)) {
                    move.setName(value);
                    result.add(copyAppendValue(move, value));
                }
                // 如果名字和移动指针带的名字不一样时，则应该找找同级别是否有一样的，这就需要父指针了
                else if (!move.getName().equals(value)) {
                    List<T> currentList = pre.getChildren();
                    if (CollectionUtils.isEmpty(currentList)) {
                        // 需要进行新增节点
                        T append = copyPreAppendValue(pre, value);
                        result.add(append);
                        pre.setChildren(Lists.newArrayList(append));
                        move = append;
                    } else {
                        // 如果找到了，则代表可以继续了
                        if (currentList.stream().anyMatch(e -> e.getName().equals(value))) {
                            move = currentList.stream().filter(e -> e.getName().equals(value)).findFirst().get();
                        } else {
                            // 找不到就应该进行新增了
                            T append = copyPreAppendValue(pre, value);
                            result.add(append);
                            pre.getChildren().add(append);
                            move = append;
                        }
                    }
                }
                pre = move;

            }
        }
        return result;
    }

    private static <T extends TreeEntity> T copyPreAppendValue(T pre, String value) {
        T append = copyAppendValue(pre, value);
        append.setId(IdGen.uuid());
        append.setParentIds(TreeLevelUtil.calculateLevel(append.getParentIds(), append.getId()));
        append.setIsNewRecord(true);
        append.setParent(pre);
        return append;
    }

    @SneakyThrows
    private static <T extends TreeEntity> T copyAppendValue(T source, String name) {
        T t = (T) source.getClass().newInstance();
        BeanUtils.copyProperties(source, t);
        t.setName(name);
        t.setIsNewRecord(true);
        return t;
    }

    private static <T extends TreeEntity> List<T> toLineEntity(List<List<String>> data, List<T> old) {
        return Lists.newArrayList();
    }

    public static class ExcelData {

        public int with;

        public List<List<String>> data;

        public static ExcelData build() {
            return new ExcelData();
        }
    }

}
